package com.jintian.smart.kernel.common.expr.function;

import com.jintian.smart.kernel.common.util.MethodUtil;
import com.jintian.smart.kernel.core.ExprFunction;
import com.jintian.smart.kernel.core.annotation.expr.ExprFn;
import com.jintian.smart.kernel.core.annotation.expr.ExprFns;
import com.jintian.smart.kernel.common.expr.DefaultFunctionRepository;
import com.jintian.smart.kernel.core.expr.IFunctionRepository;
import com.jintian.smart.kernel.core.expr.IllegalExpressionException;
import com.jintian.smart.kernel.common.expr.datameta.BaseDataMeta;
import com.jintian.smart.kernel.common.expr.datameta.Constant;
import com.jintian.smart.kernel.common.expr.datameta.Reference;
import com.jintian.smart.kernel.common.expr.format.reader.FunctionTypeReader;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 表达式函数执行器，含函数注册
 */
public class FunctionExecution {

    private final Map<String, ExprFunction> functionMap = new HashMap<String, ExprFunction>();

    private static ThreadLocal<FunctionExecution> theadLocalExecution = new ThreadLocal<FunctionExecution>();

    public final static FunctionExecution DEFAULT_INSTANCE = new FunctionExecution();

    static final class InnerFunction {

        @ExprFns({@ExprFn(code = "asList"), @ExprFn(code = "" + FunctionTypeReader.LIST_START)})
        public static List<Object> asList(Object... objects) {
            return new ArrayList<Object>(Arrays.asList(objects));
        }

        public static Object invokeMethod(Object target, String methodName, Object... params) {
            return MethodUtil.invokeMethod(target, methodName, params);
        }
    }

    public FunctionExecution() {
        this(new DefaultFunctionRepository());
    }

    public FunctionExecution(IFunctionRepository IFunctionRepository) {
        this.setFunctionRepository(IFunctionRepository);
    }

    public void setFunctionRepository(IFunctionRepository IFunctionRepository) {
        IFunctionRepository.registerStaticMethod(InnerFunction.class);
        this.functionMap
                .putAll(IFunctionRepository.getFunctions().stream().collect(Collectors.toMap(e -> e.getName(), e -> e)));
    }

    private static ExprFunction getFunction(String name) {
        FunctionExecution instance = theadLocalExecution.get();
        ExprFunction fn = (instance != null ? instance : DEFAULT_INSTANCE).functionMap.get(name);
        if (fn == null) {
            throw new IllegalExpressionException("未注册名称为" + name + "的表达式函数");
        }
        return fn;
    }

    private static Object invokeFunction(String name, Object[] parameters)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        ExprFunction fn = getFunction(name);
        return fn.getMethod().invoke(fn.getTarget(), parameters);
    }

    public static void bind(FunctionExecution fe) {
        theadLocalExecution.set(fe);
    }

    public static void unbind() {
        theadLocalExecution.set(null);
    }

    /**
     * 根据函数名、参数数组，执行操作，并返回结果Token
     *
     * @param functionName 函数名
     * @param position
     * @param args         注意args中的参数由于是从栈中按LIFO顺序弹出的，所以必须从尾部倒着取数
     * @return
     * @throws IllegalExpressionException
     */
    public static Constant execute(String functionName, int position, Constant[] args)
            throws IllegalExpressionException {
        if (functionName == null) {
            throw new IllegalArgumentException("函数名为空");
        }
        if (args == null) {
            throw new IllegalArgumentException("函数参数列表为空");
        }
        for (int i = 0; i < args.length; i++) {
            // 如果参数为引用类型，则执行引用
            if (args[i].isReference()) {
                Reference ref = (Reference) args[i].getDataValue();
                args[i] = ref.execute();
            }
        }

        // 转化方法参数
        Object[] parameters;
        try {
            parameters = convertParameters(functionName, position, args);
        } catch (Exception e) {
            throw new IllegalArgumentException("函数\"" + functionName + "\"执行异常", e);
        }
        try {
            Object result = invokeFunction(functionName, parameters);
            if (result instanceof Boolean) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_BOOLEAN, result);

            } else if (result instanceof Date) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_DATE, result);

            } else if (result instanceof Double) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_DOUBLE, result);

            } else if (result instanceof Float) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_FLOAT, result);

            } else if (result instanceof Integer) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_INT, result);

            } else if (result instanceof Long) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_LONG, result);

            } else if (result instanceof String) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_STRING, result);

            } else if (result instanceof Collection) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_LIST, result);
            } else if (result instanceof Map) {
                return new Constant(BaseDataMeta.DataType.DATATYPE_MAP, result);
            } else {
                return new Constant(BaseDataMeta.DataType.DATATYPE_OBJECT, result);

            }
        } catch (NoSuchMethodException e) {
            // 抛异常
            throw new IllegalStateException("函数\"" + functionName + "\"不存在或参数类型不匹配", e);
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("函数\"" + functionName + "\"参数类型不匹配", e);
        } catch (Exception e) {
            throw new IllegalStateException("函数\"" + functionName + "\"执行错误", e);

        }
    }

    /**
     * 检查函数和参数是否合法，是可执行的 如果合法，则返回含有执行结果类型的Token 如果不合法，则返回null
     *
     * @param functionName
     * @param position
     * @param args         注意args中的参数由于是从栈中按LIFO顺序弹出的，所以必须从尾部倒着取数
     * @return
     * @throws IllegalExpressionException
     */
    public static Constant verify(String functionName, int position, BaseDataMeta[] args)
            throws IllegalExpressionException {
        if (functionName == null) {
            throw new IllegalArgumentException("函数名为空");
        }
        if ("{".equals(functionName)) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_LIST, null);
        }

        // 通过方法名和参数数组，获取方法，及方法的返回值，并转化成ExpressionToken
        ExprFunction fn = getFunction(functionName);
        // 校验方法参数类型
        if (fn.getMethod().isVarArgs()) {
            if (args.length < fn.getMethod().getParameterCount() - 1)
                throw new IllegalExpressionException("函数\"" + functionName + "\"最少需要"
                        + (fn.getMethod().getParameterCount() - 1) + "个参数,实际参数" + args.length + "个", functionName,
                        position);
        } else {
            if (args.length < fn.getMethod().getParameterCount())
                throw new IllegalExpressionException("函数\"" + functionName + "\"需要"
                        + (fn.getMethod().getParameterCount() - 1) + "个参数,实际参数" + args.length + "个", functionName,
                        position);

        }
        Class<?> returnType = fn.getMethod().getReturnType();
        // 转换成ExpressionToken
        if (BigDecimal.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_BIGDECIMAL, null);
        } else if (boolean.class == returnType || Boolean.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_BOOLEAN, Boolean.FALSE);

        } else if (Date.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_DATE, null);

        } else if (double.class == returnType || Double.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_DOUBLE, Double.valueOf(0.0));

        } else if (float.class == returnType || Float.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_FLOAT, Float.valueOf(0.0f));

        } else if (int.class == returnType || Integer.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_INT, Integer.valueOf(0));

        } else if (long.class == returnType || Long.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_LONG, Long.valueOf(0L));

        } else if (String.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_STRING, null);

        } else if (List.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_LIST, null);

        } else if (Map.class == returnType) {
            return new Constant(BaseDataMeta.DataType.DATATYPE_MAP, null);

        } else {
            return new Constant(BaseDataMeta.DataType.DATATYPE_OBJECT, null);
        }
    }

    /**
     * 函数参数转化，函数参数是倒序的
     *
     * @param args
     * @return
     * @throws NoSuchMethodException
     */
    private static Object[] convertParameters(String functionName, int position, Constant[] args) {
        // 参数为空，返回空数组
        if (args == null) {
            return new Object[0];
        }

        // 转化方法参数类型数组
        Method method = getFunction(functionName).getMethod();

        Object[] parameters = new Object[method.getParameterCount()];
        int argIdx = args.length - 1;
        int max = method.getParameterCount() - (method.isVarArgs() ? 1 : 0);
        Class<?>[] parametersType = method.getParameterTypes();
        for (int i = 0; i < max; i++) {
            Class<?> javaType = parametersType[i];
            parameters[i] = args[argIdx--].cast(javaType);
        }
        if (method.isVarArgs()) {
            Class<?> javaType = parametersType[method.getParameterCount() - 1];
            Object[] arrayParam = (Object[]) Array.newInstance(javaType.getComponentType(),
                    args.length - parameters.length + 1);
            parameters[method.getParameterCount() - 1] = arrayParam;
            for (int i = 0; i < arrayParam.length; i++) {
                arrayParam[i] = args[argIdx--].cast(javaType.getComponentType());
            }
        }
        return parameters;
    }
}
